package net.sudot.chess.web.advice;

import net.sudot.chess.business.dto.ErrorResponse;
import net.sudot.chess.exception.BusinessException;
import net.sudot.chess.exception.SystemException;
import net.sudot.commons.json.JsonUtils;
import net.sudot.commons.lock.UnlockException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authz.AuthorizationException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.util.StringUtils;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.util.WebUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;

/**
 * 控制器全局异常处理
 *
 * @author tangjialin on 2018-04-01.
 */
@ControllerAdvice
public class GlobalExceptionHandler {
    public static final String ERROR_URI = "/error";
    public static final String ERROR_VIEW = "forward:" + ERROR_URI;

    private static final Logger LOGGER = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    protected String handle(HttpStatus httpStatus, String message, Exception e, HttpServletRequest request, HttpServletResponse response) {
        /**
         * "timestamp" -> "1541654376153"
         * "status" -> "406"
         * "error" -> "Not Acceptable"
         * "message" -> "Could not parse 'Accept' header [3]: Invalid mime type "3": does not contain '/'"
         * "path" -> "/error"
         */
        request.setAttribute(WebUtils.ERROR_STATUS_CODE_ATTRIBUTE, httpStatus.value());
        request.setAttribute(WebUtils.ERROR_MESSAGE_ATTRIBUTE, message);
        return ERROR_VIEW;
    }

    /**
     * 处理所有接口数据验证异常
     *
     * @param e        异常信息
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return 返回响应结果
     */
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public String handleMethodArgumentNotValidException(MethodArgumentNotValidException e, HttpServletRequest request, HttpServletResponse response) {
        String message = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
        LOGGER.warn("handleMethodArgumentNotValidException {} {}", request.getRequestURI(), message);
        return handle(HttpStatus.BAD_REQUEST, message, e, request, response);
    }

    /**
     * 处理身份信息认证异常(即:401 未登录,或登录信息已过期)
     *
     * @param e        异常信息
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return 返回响应结果
     */
    @ExceptionHandler(AuthenticationException.class)
    public String handleAuthenticationException(AuthorizationException e, HttpServletRequest request, HttpServletResponse response) {
        LOGGER.warn("handleAuthenticationException {} {}", request.getRequestURI(), e.getMessage());
        return handle(HttpStatus.UNAUTHORIZED, "你可能太长时间没有操作了, 出于安全考虑, 系统拒绝了你的请求。", e, request, response);
    }

    /**
     * 处理权限认证异常(即:403 已登录,但是没有访问权限)
     *
     * @param e        异常信息
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return 返回响应结果
     */
    @ExceptionHandler(AuthorizationException.class)
    public String handleAuthorizationException(AuthorizationException e, HttpServletRequest request, HttpServletResponse response) {
        LOGGER.warn("handleAuthorizationException {} {}", request.getRequestURI(), e.getMessage());
        return handle(HttpStatus.FORBIDDEN, "权限不足", e, request, response);
    }

    /**
     * 处理所有请求类型错误异常
     *
     * @param e        异常信息
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return 返回响应结果
     */
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public String handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e, HttpServletRequest request, HttpServletResponse response) {
        String message = e.getMessage();
        LOGGER.warn("handleHttpRequestMethodNotSupportedException {} {}", request.getRequestURI(), message);
        return handle(HttpStatus.METHOD_NOT_ALLOWED, message, e, request, response);
    }

    /**
     * 处理Accept错误(不支持的接受类型)的异常
     *
     * @param e        异常信息
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return 返回响应结果
     */
    @ExceptionHandler({HttpMediaTypeNotAcceptableException.class})
    public void handleException(HttpMediaTypeNotAcceptableException e, HttpServletRequest request, HttpServletResponse response) {
        String message = e.getMessage();
        LOGGER.warn("HttpMediaTypeNotAcceptableException {} {}", request.getRequestURI(), message);
        try (OutputStream stream = response.getOutputStream()) {
            HttpStatus httpStatus = HttpStatus.NOT_ACCEPTABLE;
            response.setStatus(httpStatus.value());
            response.setContentType(MediaType.APPLICATION_JSON_VALUE);
            ErrorResponse errorResponse = new ErrorResponse(httpStatus, request.getRequestURI(), StringUtils.isEmpty(message) ? "No message available" : message);
            JsonUtils.toJson(stream, errorResponse);
        } catch (Exception exception) {
            LOGGER.error("HttpMediaTypeNotAcceptableException 不支持的接受类型 {} {}", request.getRequestURI(), message);
        }
    }

    /**
     * 处理所有业务异常
     *
     * @param e        异常信息
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return 返回响应结果
     */
    @ExceptionHandler({BusinessException.class, IllegalArgumentException.class})
    public String handleBusinessException(Exception e, HttpServletRequest request, HttpServletResponse response) {
        String message = e.getMessage();
        LOGGER.warn("handleBusinessException {} {}", request.getRequestURI(), message);
        return handle(HttpStatus.PRECONDITION_FAILED, message, e, request, response);
    }

    /**
     * 处理分布式锁的异常
     *
     * @param e        异常信息
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return 返回响应结果
     */
    @ExceptionHandler({UnlockException.class})
    public String handleUnlockException(UnlockException e, HttpServletRequest request, HttpServletResponse response) {
        LOGGER.warn("handleUnlockException {} {}", request.getRequestURI(), e.getMessage());
        return handle(HttpStatus.UNPROCESSABLE_ENTITY, "系统繁忙,请稍候重试", e, request, response);
    }

    /**
     * 处理所有自定义的系统异常
     *
     * @param e        异常信息
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return 返回响应结果
     */
    @ExceptionHandler(SystemException.class)
    public String handleSystemException(SystemException e, HttpServletRequest request, HttpServletResponse response) {
        String message = e.getMessage();
        LOGGER.warn("handleSystemException {} {}", request.getRequestURI(), message);
        return handle(HttpStatus.UNPROCESSABLE_ENTITY, message, e, request, response);
    }

    /**
     * 处理所有不可知的异常
     *
     * @param e        异常信息
     * @param request  HttpServletRequest
     * @param response HttpServletResponse
     * @return 返回响应结果
     */
    @ExceptionHandler({Exception.class})
    public String handleException(Exception e, HttpServletRequest request, HttpServletResponse response) {
        LOGGER.error("handleException {} {}", request.getRequestURI(), e.getMessage(), e);
        return handle(HttpStatus.INTERNAL_SERVER_ERROR, "系统错误", e, request, response);
    }

}
